/* @flow */

import config from '../config'
import { initProxy } from './proxy'
import { initState } from './state'
import { initRender } from './render'
import { initEvents } from './events'
import { mark, measure } from '../util/perf'
import { initLifecycle, callHook } from './lifecycle'
import { initProvide, initInjections } from './inject'
import { extend, mergeOptions, formatComponentName } from '../util/index'

let uid = 0

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      startTag = `vue-perf-start:${vm._uid}`
      endTag = `vue-perf-end:${vm._uid}`
      mark(startTag)
    }

    // a flag to avoid this being observed
    // 用_isVue标记这是一个Vue的实例对象，它不可以被observed
    vm._isVue = true
    // merge options
    if (options && options._isComponent) { // 子组件options合并, 子组件初始化
      // 优化内部组件实例化,因为动态选项合并非常慢，而且没有内部组件选项需要特殊处理。
      // optimize internal component instantiation  
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.

      //  参考：componentVNodeHooks --> init钩子函数内 --> createComponentInstanceForVnode函数中options的定义
      // 作用：将通过 createComponentInstanceForVnode函数传入的几个参数合并到内部的选项 $options 里了
      initInternalComponent(vm, options)
    } else {
      // 将Vue.options，以及用户传入的options合并，并扩展到vm.$options上
      // Vue.options定义在global-api中:包括components(KeepAlive,Transition,TransitionGroup)、directives(model,show)、filters，_base（指向Vue实例）
      // options合并选项包括：
      // 1、data、watch、(props、methods、computed、injecte)、provide、
      // 2、LIFECYCLE_HOOKS生命周期合并
      // 3、组件components、指令directives、过滤器filters的合并策略
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    // 初始化父子组件关系,包括$children、$parent
    /**
     * 1、子组件初始化，调用componentVNodeHooks.init中 -> 调用createComponentInstanceForVnode创建子组件实例，并将当前子组件的父实例activeInstance传入内部
     * 2、createComponentInstanceForVnod在函数内部，将父实例vm，存放到InternalComponentOptions.parent属性上。
     * 3、调用_init -> initInternalComponent方法内，将父实例vm，存放到子组件的vm.$options.parent上
     * 4、调用_init -> initLifecycle 初始化父子关系，并定义$root属性。
     */
    initLifecycle(vm)
    // 初始化组件事件，即listeners
    // 将父组件传递过来的自定义事件，转化为$on,$off,$once等方法： const listeners = vm.$options._parentListeners
    initEvents(vm)
    // 定义了如下方法：
    // 1、vm._c: 内置createElement方法，最后一个参数为true：SIMPLE_NORMALIZE
    //  vm.$createElement: 手写时使用的createElement方法,最后一个参数为true：ALWAYS_NORMALIZE
    // 2、定义了$attrs、$listeners为'浅层'响应式属性：
    // 3、定义了$slots、$scopedSlots，其中 $slots 是立刻赋值的，但是 $scopedSlots 初始化的时候是一个 emptyObject，// todo???
    // 直到组件的 vm._render 过程中才会通过 normalizeScopedSlots(Vue.prototype._render阶段) 去把真正的 $scopedSlots 整合后挂到 vm 上。
    initRender(vm)
    callHook(vm, 'beforeCreate')
    // provide ,inject核心就是$parent，通过while循环一直向上查找
    // 查找父组件的vm._provided上对应的key值，并将vm.key设置为响应式的：defineReactive(vm, key, result[key])
    initInjections(vm) // resolve injections before data/props， 在data/props之前，处理injections
    initState(vm); // 初始化data
    // 将provide的对象，放到vm._provided上，即vm._provided = provideObject || provideFunc(), 这样就可以根据key获取到inject的数据
    initProvide(vm) // resolve provide after data/props， 在data/props之后，处理provide
    callHook(vm, 'created')

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
      vm._name = formatComponentName(vm, false)
      mark(endTag)
      measure(`vue ${vm._name} init`, startTag, endTag)
    }

    if (vm.$options.el) {
      // $mount定义：
      //   runtime: src\platforms\web\runtime\index.js 42行
      //   runtime-with-compiler: src\platforms\web\entry-runtime-with-compiler.js 18行
      // $mount内部调用mountComponent: src\core\instance\lifecycle.js 157行
      vm.$mount(vm.$options.el)
    }
  }
}

export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  // 将子组件options，通过原型链与Sub.options关联
  // 而Sub.options在Vue.extend阶段，执行mergeOptions时，将Sub.options.components(或directives、filters)通过原型链，指向了Vue.options.components(或directives、filters),参考mergeAssets(util/options 187行)
  // 当调用resolveAsset(context.$options, 'components', tag)时，先从自己父类options查找组件，然后通过原型链查找全局组件。
  // resolveAsset：vdom/create-element.js 119行
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.

  // 以下变量，在createComponentInstanceForVnode中赋值
  // 父级vnode
  const parentVnode = options._parentVnode
  // 父级构造函数
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  // 获取父组件的componentOptions，包括propsData、listeners、children
  // <App age={100} title='标题' on={ {test: this.handler} }><div>children</div></App>
  // propsData: 传递给子组件的prop属性：age、title(子组件中必须在props中顶替)
  // listeners：传递给子组件的事件：{ test: fn }
  // children：传递给子组件的children，通常是插槽: [VNode]
  const vnodeComponentOptions = parentVnode.componentOptions
  // 从vnodeComponentOptions中提取父传给子的props数据, 参考src\core\vdom\create-component.js 178行的“extractPropsFromVNodeData”方法
  opts.propsData = vnodeComponentOptions.propsData
  // 1、vdom/create-component.js 210行data.on取事件的值、250行，存放到componentOptions的listeners属性上，
  // 2、在子组件初始化时，调用initInternalComponent，将其赋值给opts._parentListeners，
  // 3、初始化时，调用initEvents方法更新事件(内部使用$on方法))，当我们需要触发时，调用$emit('eventName')即可。其内部就是实现了一个发布订阅，对应的事件，存放在vm._events[eventName]上。
  // 父传给子的自定义listeners事件，
  opts._parentListeners = vnodeComponentOptions.listeners 
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

export function resolveConstructorOptions (Ctor: Class<Component>) {
  // Vue初始化时的options(initGlobal中定义的Vue.options)
  // 包括components(KeepAlive,Transition,TransitionGroup)、directives(model,show)、filters，_base
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
  let modified
  const latest = Ctor.options
  const sealed = Ctor.sealedOptions
  for (const key in latest) {
    if (latest[key] !== sealed[key]) {
      if (!modified) modified = {}
      modified[key] = latest[key]
    }
  }
  return modified
}
